/******************************************************************************
 * JBoss, a division of Red Hat                                               *
 * Copyright 2011, Red Hat Middleware, LLC, and individual                    *
 * contributors as indicated by the @authors tag. See the                     *
 * copyright.txt in the distribution for a full listing of                    *
 * individual contributors.                                                   *
 *                                                                            *
 * This is free software; you can redistribute it and/or modify it            *
 * under the terms of the GNU Lesser General Public License as                *
 * published by the Free Software Foundation; either version 2.1 of           *
 * the License, or (at your option) any later version.                        *
 *                                                                            *
 * This software is distributed in the hope that it will be useful,           *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU           *
 * Lesser General Public License for more details.                            *
 *                                                                            *
 * You should have received a copy of the GNU Lesser General Public           *
 * License along with this software; if not, write to the Free                *
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA         *
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.                   *
 ******************************************************************************/
package org.jboss.gatein.qa.picketlink.hibernate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.jboss.gatein.qa.picketlink.hibernate.objects.HibernateGroup;
import org.jboss.gatein.qa.picketlink.hibernate.objects.HibernateObject;
import org.jboss.gatein.qa.picketlink.hibernate.objects.HibernateUser;
import org.picketlink.idm.common.exception.IdentityException;
import org.picketlink.idm.impl.api.SimpleAttribute;
import org.picketlink.idm.impl.store.FeaturesMetaDataImpl;
import org.picketlink.idm.impl.types.SimpleIdentityObject;
import org.picketlink.idm.impl.types.SimpleIdentityObjectRelationship;
import org.picketlink.idm.impl.types.SimpleIdentityObjectRelationshipType;
import org.picketlink.idm.impl.types.SimpleIdentityObjectType;
import org.picketlink.idm.spi.configuration.IdentityStoreConfigurationContext;
import org.picketlink.idm.spi.configuration.metadata.IdentityObjectAttributeMetaData;
import org.picketlink.idm.spi.configuration.metadata.IdentityObjectTypeMetaData;
import org.picketlink.idm.spi.configuration.metadata.IdentityStoreConfigurationMetaData;
import org.picketlink.idm.spi.exception.OperationNotSupportedException;
import org.picketlink.idm.spi.model.IdentityObject;
import org.picketlink.idm.spi.model.IdentityObjectAttribute;
import org.picketlink.idm.spi.model.IdentityObjectCredential;
import org.picketlink.idm.spi.model.IdentityObjectRelationship;
import org.picketlink.idm.spi.model.IdentityObjectRelationshipType;
import org.picketlink.idm.spi.model.IdentityObjectType;
import org.picketlink.idm.spi.search.IdentityObjectSearchCriteria;
import org.picketlink.idm.spi.store.FeaturesMetaData;
import org.picketlink.idm.spi.store.IdentityObjectSearchCriteriaType;
import org.picketlink.idm.spi.store.IdentityStore;
import org.picketlink.idm.spi.store.IdentityStoreInvocationContext;
import org.picketlink.idm.spi.store.IdentityStoreSession;

/**
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 * @version $Revision$
 */
public class PicketlinkHibernateExampleIdentityStoreImpl implements IdentityStore
{
   private static Logger log = Logger.getLogger(PicketlinkHibernateExampleIdentityStoreImpl.class.getName());
   
   public static final String HIBERNATE_SESSION_FACTORY_REGISTRY_NAME = "hibernateSessionFactoryRegistryName";

   public static final String HIBERNATE_CONFIGURATION = "hibernateConfiguration";

   public static final String ADD_HIBERNATE_MAPPINGS = "addHibernateMappings";

   public static final String HIBERNATE_SESSION_FACTORY_JNDI_NAME = "hibernateSessionFactoryJNDIName";

   public static final String IS_REALM_AWARE = "isRealmAware";
   
   public static final String MANAGE_TRANSACTION_DURING_BOOTSTRAP = "manageTransactionDuringBootstrap";

   public static final String ALLOW_NOT_CASE_SENSITIVE_SEARCH = "allowNotCaseSensitiveSearch";

   public static final String DEFAULT_REALM_NAME = PicketlinkHibernateExampleIdentityStoreImpl.class.getName() + ".DEFAULT_REALM";

   public static final String CREDENTIAL_TYPE_PASSWORD = "PASSWORD";

   public static final String CREDENTIAL_TYPE_BINARY = "BINARY"; 
   
   public static final String ATTR_USER_PASSWORD = "password";
   public static final String ATTR_USER_FIRST_NAME = "firstName";
   public static final String ATTR_USER_LAST_NAME = "lastName";
   public static final String ATTR_USER_EMAIL = "email";
   public static final String ATTR_USER_CREATED_DATE = "createdDate";
   public static final String ATTR_USER_LAST_LOGIN_TIME = "lastLoginTime";
   
   public static final String ATTR_GROUP_LABEL = "label";
   public static final String ATTR_GROUP_DESCRIPTION = "description";   

   private String id;

   private FeaturesMetaData supportedFeatures;

   private SessionFactory sessionFactory;

   private boolean isRealmAware = false;

   private boolean isAllowNotCaseSensitiveSearch = false;

   private boolean isManageTransactionDuringBootstrap = true;

   // TODO: rewrite this into some more handy object
   private IdentityStoreConfigurationMetaData configurationMD;

   private static Set<IdentityObjectSearchCriteriaType> supportedIdentityObjectSearchCriteria =
      new HashSet<IdentityObjectSearchCriteriaType>();

   private static Set<String> supportedCredentialTypes = new HashSet<String>();

   // <IdentityObjectType name, Set<Attribute name>>
   private Map<String, Set<String>> attributeMappings = new HashMap<String, Set<String>>();

   // <IdentityObjectType name, <Attribute name, MD>
   private Map<String, Map<String, IdentityObjectAttributeMetaData>> attributesMetaData = new HashMap<String, Map<String, IdentityObjectAttributeMetaData>>();

   // <IdentityObjectType name, <Attribute store mapping, Attribute name>
   private Map<String, Map<String, String>> reverseAttributeMappings = new HashMap<String, Map<String, String>>();
   
   private static final long serialVersionUID = -130357352189832805L;

   static 
   {
      // List all supported criteria classes

      supportedIdentityObjectSearchCriteria.add(IdentityObjectSearchCriteriaType.ATTRIBUTE_FILTER);
      supportedIdentityObjectSearchCriteria.add(IdentityObjectSearchCriteriaType.NAME_FILTER);
      supportedIdentityObjectSearchCriteria.add(IdentityObjectSearchCriteriaType.PAGE);
      supportedIdentityObjectSearchCriteria.add(IdentityObjectSearchCriteriaType.SORT);

      // credential types supported by this impl
      supportedCredentialTypes.add(CREDENTIAL_TYPE_PASSWORD);
      supportedCredentialTypes.add(CREDENTIAL_TYPE_BINARY);
      
   }
   
//   TODO: This is only temporary workaround so that we can directly test behaviour of PicketlinkHibernateExampleIdentityStoreImpl. 
//   private static PicketlinkHibernateExampleIdentityStoreImpl instance = null;
//   
//   public static PicketlinkHibernateExampleIdentityStoreImpl getInstance()
//   {
//      return instance;
//   }
   
   public PicketlinkHibernateExampleIdentityStoreImpl(String id)
   {
      this.id = id;
           
//    instance = this; 
   }   
   
   public void bootstrap(IdentityStoreConfigurationContext configurationContext) throws IdentityException
   {
      this.configurationMD = configurationContext.getStoreConfigurationMetaData();
      id = configurationMD.getId();
      supportedFeatures = new FeaturesMetaDataImpl(configurationMD, supportedIdentityObjectSearchCriteria, true, true, new HashSet<String>());

//      String manageTransactionDuringBootstrap = configurationMD.getOptionSingleValue(MANAGE_TRANSACTION_DURING_BOOTSTRAP);

      sessionFactory = bootstrapHibernateSessionFactory(configurationContext);

      Session hibernateSession = sessionFactory.openSession();

      // Attribute mappings - helper structures

      for (IdentityObjectTypeMetaData identityObjectTypeMetaData : configurationMD.getSupportedIdentityTypes())
      {
         Set<String> names = new HashSet<String>();
         Map<String, IdentityObjectAttributeMetaData> metadataMap = new HashMap<String, IdentityObjectAttributeMetaData>();
         Map<String, String> reverseMap = new HashMap<String, String>();
         for (IdentityObjectAttributeMetaData attributeMetaData : identityObjectTypeMetaData.getAttributes())
         {
            names.add(attributeMetaData.getName());
            metadataMap.put(attributeMetaData.getName(), attributeMetaData);
            if (attributeMetaData.getStoreMapping() != null)
            {
               reverseMap.put(attributeMetaData.getStoreMapping(), attributeMetaData.getName());
            }
         }

         // Use unmodifiableSet as it'll be exposed directly 
         attributeMappings.put(identityObjectTypeMetaData.getName(), Collections.unmodifiableSet(names));
         attributesMetaData.put(identityObjectTypeMetaData.getName(), metadataMap);
         reverseAttributeMappings.put(identityObjectTypeMetaData.getName(), reverseMap);
      }

      attributeMappings = Collections.unmodifiableMap(attributeMappings);
      
      // TODO: Is this really needed when nothing is added into DB directly after start?
      if (isManageTransactionDuringBootstrap())
      {
         hibernateSession.getTransaction().begin();
      }

      if (supportedCredentialTypes != null && supportedCredentialTypes.size() > 0)
      {
         
         // Don't populate nothing directly into DB
//         try
//         {
//            populateCredentialTypes(hibernateSession, supportedCredentialTypes.toArray(new String[supportedCredentialTypes.size()]));
//         }
//         catch (Exception e)
//         {
//            if (log.isLoggable(Level.FINER))
//            {
//               log.log(Level.FINER, "Exception occurred: ", e);
//            }
//
//            throw new IdentityException("Failed to populated credential types");
//         }
      }

// .
//      if (realm == null)
//      {
//         addRealm(hibernateSession, DEFAULT_REALM_NAME);
//      }
//
//      // If store is realm aware than creat all configured realms
//
//
//
//      if (isRealmAware())
//      {
//         Set<String> realmNames = new HashSet<String>();
//
//         for (RealmConfigurationMetaData realmMD : configurationContext.getConfigurationMetaData().getRealms())
//         {
//            realmNames.add(realmMD.getId());
//         }
//
//         for (String rid : realmNames)
//         {
//            realm = (HibernateRealm)hibernateSession.createCriteria(HibernateRealm.class).
//               add(Restrictions.eq("name",rid)).setCacheable(true).uniqueResult();
//
//            if (realm == null)
//            {
//               addRealm(hibernateSession, rid);
//            }
//         }
//      }

      // TODO: Is this really needed when nothing is added into DB directly after start?
      if (isManageTransactionDuringBootstrap())
      {
         hibernateSession.getTransaction().commit();
      }

      hibernateSession.flush();
      hibernateSession.close();      
      
   }
   
   protected SessionFactory bootstrapHibernateSessionFactory(IdentityStoreConfigurationContext configurationContext) throws IdentityException
   {

      String sfJNDIName = configurationContext.getStoreConfigurationMetaData().
         getOptionSingleValue(HIBERNATE_SESSION_FACTORY_JNDI_NAME);
      String sfRegistryName = configurationContext.getStoreConfigurationMetaData().
         getOptionSingleValue(HIBERNATE_SESSION_FACTORY_REGISTRY_NAME);
      String addMappedClasses = configurationContext.getStoreConfigurationMetaData().
         getOptionSingleValue(ADD_HIBERNATE_MAPPINGS);
      String hibernateConfiguration = configurationContext.getStoreConfigurationMetaData().
         getOptionSingleValue(HIBERNATE_CONFIGURATION);

      if (sfJNDIName != null)
      {
         try
         {
            return (SessionFactory)new InitialContext().lookup(sfJNDIName);
         }
         catch (NamingException e)
         {
            if (log.isLoggable(Level.FINER))
            {
               log.log(Level.FINER, "Exception occurred: ", e);
            }

            throw new IdentityException("Cannot obtain hibernate SessionFactory from provided JNDI name: " + sfJNDIName, e);
         }
      }
      else if (sfRegistryName != null)
      {
         Object registryObject = configurationContext.getConfigurationRegistry().getObject(sfRegistryName);

         if (registryObject == null)
         {
            throw new IdentityException("Cannot obtain hibernate SessionFactory from provided registry name: " + sfRegistryName);
         }

         if (!(registryObject instanceof SessionFactory))
         {
            throw new IdentityException("Cannot obtain hibernate SessionFactory from provided registry name: " + sfRegistryName
            + "; Registered object is not an instance of SessionFactory: " + registryObject.getClass().getName());
         }

         return (SessionFactory)registryObject;


      }
      else if (hibernateConfiguration != null)
      {

         try
         {
            Configuration config = new Configuration().configure(hibernateConfiguration);


            if (addMappedClasses != null && addMappedClasses.equals("false"))
            {
               return config.buildSessionFactory();
            }
            else
            {

               return config
                  .addResource("mappings/HibernateUser.hbm.xml")
                  .addResource("mappings/HibernateGroup.hbm.xml")                 
                  .buildSessionFactory();
            }
         }
         catch (Exception e)
         {
            if (log.isLoggable(Level.FINER))
            {
               log.log(Level.FINER, "Exception occurred: ", e);
            }

            throw new IdentityException("Cannot obtain hibernate SessionFactory using provided hibernate configuration: "+ hibernateConfiguration, e);
         }

      }
      throw new IdentityException("Cannot obtain hibernate SessionFactory. None of supported options specified: "
         + HIBERNATE_SESSION_FACTORY_JNDI_NAME + ", " + HIBERNATE_SESSION_FACTORY_REGISTRY_NAME + ", " + HIBERNATE_CONFIGURATION);
   }
   
   
   public FeaturesMetaData getSupportedFeatures()
   {
      return supportedFeatures;
   }   
   
   public IdentityStoreSession createIdentityStoreSession() throws IdentityException
   {
      try
      {
         return new ExampleHibernateIdentityStoreSessionImpl(sessionFactory);
      }
      catch (Exception e)
      {
         if (log.isLoggable(Level.FINER))
            {
               log.log(Level.FINER, "Exception occurred: ", e);
            }

         throw new IdentityException("Failed to obtain Hibernate SessionFactory",e);
      }
   }

   public IdentityObject createIdentityObject(IdentityStoreInvocationContext invocationCtx, String name,
         IdentityObjectType identityObjectType) throws IdentityException
   {
      return createIdentityObject(invocationCtx, name, identityObjectType, null);
   }

   public IdentityObject createIdentityObject(IdentityStoreInvocationContext arg0, String name,
         IdentityObjectType ioType, Map<String, String[]> attributes) throws IdentityException
   {
      ExampleIdentityObjectTypeEnum typeInternal = getInternalType(ioType); 
      
      // validate if object already exists
      int currentCount = findCountOfObjectFromDBByTypeAndName(name, ioType);
      if (currentCount != 0)
      {
         throw new IdentityException("Hibernate object with name=" + name + " and type=" + ioType.getName() + " already exists!");
      }
      
      HibernateObject hObj = buildPopulatedHibernateObject(name, typeInternal, attributes);
      persistHibernateObject(hObj);
      
      return new SimpleIdentityObject(hObj.getName(), String.valueOf(hObj.getId()), ioType);
   }

   public IdentityObjectRelationship createRelationship(IdentityStoreInvocationContext invocationCxt,
         IdentityObject fromIdentity, IdentityObject toIdentity, IdentityObjectRelationshipType relationshipType,
         String relationshipName, boolean createNames) throws IdentityException
   {
      // fromIdentity is parent and toIdentity is child.
      
      // fromIdentity must be group
      HibernateObject hObjectFrom = findHibernateObjectFromIdentityObject(fromIdentity);
      HibernateObject hObjectTo = findHibernateObjectFromIdentityObject(toIdentity);
      
      HibernateGroup parentGroup;

      // fromIdentity must be group
      if (hObjectFrom instanceof HibernateUser)
      {
         throw new IdentityException("Parent object " + hObjectFrom + " is not group!");
      }
      parentGroup = (HibernateGroup)hObjectFrom;
      
      // in this case we need to add user into group.
      if (hObjectTo instanceof HibernateUser)
      {         
         HibernateUser hUserTo = (HibernateUser)hObjectTo;
         
         // TODO: Is it better way to add user into group with hibernate without need to select all groups of particular user?
         hUserTo.getGroups().add(parentGroup);
      }
      else
      {
         HibernateGroup hChildGroup = (HibernateGroup)hObjectTo;
         hChildGroup.setParentGroup(parentGroup);
      }
      
      return new SimpleIdentityObjectRelationship(fromIdentity, toIdentity, relationshipName, relationshipType);
   }

   public String createRelationshipName(IdentityStoreInvocationContext ctx, String name) throws IdentityException,
         OperationNotSupportedException
   {      
      // relationships not actively supported for us
      return name;
   }

   public IdentityObject findIdentityObject(IdentityStoreInvocationContext invocationContext, String id)
         throws IdentityException
   {
      Session session = getHibernateSession();
      
      HibernateObject hObj;
      IdentityObjectType ioType; 
         
      try
      {
         // first try table user
         hObj = (HibernateObject)session.get(HibernateUser.class, new Long(id));
         
         // then try table group
         if (hObj == null)
         {
            hObj = (HibernateObject)session.get(HibernateGroup.class, new Long(id));
                        
            ioType = new SimpleIdentityObjectType(getGroupTypeOfHibernateGroup((HibernateGroup)hObj));
         }
         else
         {
            ioType = new SimpleIdentityObjectType("USER");
         }
      }
      catch (HibernateException exc)
      {
         throw new IdentityException("Can't find object with id " + id, exc);
      }
      
      if (hObj == null)
      {
         return null;
      }
      else
      {
         return new SimpleIdentityObject( hObj.getName(), String.valueOf(hObj.getId()), ioType);
      }      
   }

   public IdentityObject findIdentityObject(IdentityStoreInvocationContext invocationContext, String name,
         IdentityObjectType identityObjectType) throws IdentityException
   {
      ExampleIdentityObjectTypeEnum typeInternal = getInternalType(identityObjectType); 
      
      Session session = getHibernateSession();
      HibernateObject hObj;
      if (typeInternal.equals(ExampleIdentityObjectTypeEnum.USER))
      {         
         hObj = (HibernateObject)session.createCriteria(HibernateUser.class)
            .add(Restrictions.eq("name", name))            
            .setCacheable(true)
            .uniqueResult();     
         
      }
      else
      {
         hObj = (HibernateObject)session.createCriteria(HibernateGroup.class)
         .add(Restrictions.eq("name", name))         
         .setCacheable(true)
         .uniqueResult();          
      }               
         
      if (hObj == null)
      {
         return null;
      }
      return new SimpleIdentityObject(hObj.getName(), String.valueOf(hObj.getId()), identityObjectType);
   }

   public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext invocationCtx, IdentityObjectType identityType,
         IdentityObjectSearchCriteria criteria) throws IdentityException
   {
      ExampleIdentityObjectTypeEnum typeInternal = getInternalType(identityType); 
      
      Criteria hc = convertCriteriaForFindingHibernateObjects(criteria, typeInternal); 
      List<HibernateObject> result = hc.list();
      Hibernate.initialize(result); 
      
      List<IdentityObject> finalResult = new ArrayList<IdentityObject>();
      for (HibernateObject hObj : result)
      {
         finalResult.add(new SimpleIdentityObject(hObj.getName(), String.valueOf(hObj.getId()), identityType));         
      }
      
      return finalResult;
   }

   public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext invocationCxt, IdentityObject identity,
         IdentityObjectRelationshipType relationshipType, boolean parent, IdentityObjectSearchCriteria criteria)
         throws IdentityException
   {
      HibernateObject ourObject = findHibernateObjectFromIdentityObject(identity);
      
      if (ourObject == null)
      {
         return null;
      }
      
      StringBuilder hql1 = new StringBuilder();
      StringBuilder hql2 = new StringBuilder();
      
      boolean orderByName = false;
      boolean ascending = true;

      if (criteria != null && criteria.isSorted())
      {
         orderByName = true;
         ascending = criteria.isAscending();
      }
      
      // We are interested in objects, which are children of our object. This means that our Object needs to be group.
      if (parent)
      {
         if (!(ourObject instanceof HibernateGroup))
         {
            throw new IdentityException("Object with id " + ourObject + " is not group!");
         }
         
         // 
         String query1 = "select distinct hObject from HibernateGroup hObject where hObject.parentGroup = :ourObject";
         String query2 = "select distinct hObject from HibernateUser  hObject where :ourObject in elements(hObject.groups)";
         
         // now decide if we are interested only in child groups, or only in users, or in both
         if (relationshipType != null && relationshipType.getName().equals("JBOSS_IDENTITY_MEMBERSHIP"))
         {
            hql1.append(query1);
         }
         else if (relationshipType != null && relationshipType.getName().equals("JBOSS_IDENTITY_ROLE"))
         {
            hql1.append(query2);
         }
         else if (relationshipType == null)
         {
            hql1.append("select distinct hObject from HibernateGroup hObject where hObject.parentGroup = :ourObject");
            hql2.append("select distinct hObject from HibernateUser  hObject where :ourObject in elements(hObject.groups)");
         }
         else
         {
            throw new IdentityException("Unsupported relationship type " + relationshipType);
         }
      }
      // we are interested in parent of our Object
      else
      {
         if (ourObject instanceof HibernateGroup)
         {
            hql1.append("select distinct hObject from HibernateGroup hObject where :ourObject in elements(hObject.childGroups)");
         }     
         else
         {
            hql1.append("select distinct hObject from HibernateGroup hObject where :ourObject in elements(hObject.users)");
         }
      }            
      
      if (criteria != null && criteria.getFilter() != null)
      {
         hql1.append(" and hObject.name like :name");
         hql2.append(" and hObject.name like :name");
      }
      
      // ordering by attributes of user
      Map<String, String[]> attributes = criteria == null ? new HashMap<String, String[]>() : criteria.getValues();
      Map<String, String> filteredAttributesForUsersSearch = new HashMap<String, String>();      
      Map<String, String> filteredAttributesForGroupsSearch = new HashMap<String, String>();     
//      filteredAttributesForUsersSearch.put(ATTR_USER_PASSWORD, getAttributeValueFromMap(attributes, ATTR_USER_PASSWORD));
      filteredAttributesForUsersSearch.put(ATTR_USER_FIRST_NAME, getAttributeValueFromMap(attributes, ATTR_USER_FIRST_NAME));
      filteredAttributesForUsersSearch.put(ATTR_USER_LAST_NAME, getAttributeValueFromMap(attributes, ATTR_USER_LAST_NAME));
      filteredAttributesForUsersSearch.put(ATTR_USER_EMAIL, getAttributeValueFromMap(attributes, ATTR_USER_EMAIL));
      filteredAttributesForUsersSearch.put(ATTR_USER_CREATED_DATE, getAttributeValueFromMap(attributes, ATTR_USER_CREATED_DATE));
      filteredAttributesForUsersSearch.put(ATTR_USER_LAST_LOGIN_TIME, getAttributeValueFromMap(attributes, ATTR_USER_LAST_LOGIN_TIME));

      filteredAttributesForGroupsSearch.put(ATTR_GROUP_LABEL, getAttributeValueFromMap(attributes, ATTR_GROUP_LABEL));
      filteredAttributesForGroupsSearch.put(ATTR_GROUP_DESCRIPTION, getAttributeValueFromMap(attributes, ATTR_GROUP_DESCRIPTION));
      
      
      for (Map.Entry<String, String> entry : filteredAttributesForUsersSearch.entrySet())
      {
         if (entry.getValue() != null)
         {
            hql1.append(" and hObject." + entry.getKey() + " like '" + entry.getValue() + "'");
         }
      }    
      for (Map.Entry<String, String> entry : filteredAttributesForGroupsSearch.entrySet())
      {
         if (entry.getValue() != null)
         {
            hql2.append(" and hObject." + entry.getKey() + " like '" + entry.getValue() + "'");
         }
      }   
      
      if (orderByName)
      {
         hql1.append(" order by hObject.name");
         hql2.append(" order by hObject.name");
         if (ascending)
         {
            hql1.append(" asc");
            hql2.append(" asc");
         }
      }      
      
      System.out.println("HQLLL: " + hql1.toString());
      Query q1 = getHibernateSession().createQuery(hql1.toString())
      .setParameter("ourObject", ourObject).setCacheable(true);
      
      // hql2 may be null, so we need to check
      Query q2 = null;
      String hql2s = hql2.toString();
      if (hql2s.startsWith("select"))
      {
         q2 = getHibernateSession().createQuery(hql2s)
         .setParameter("ourObject", ourObject).setCacheable(true);          
      }      
      
      if (criteria != null && criteria.getFilter() != null)
      {
         q1.setParameter("name", criteria.getFilter().replaceAll("\\*", "%"));
         if (q2 != null)
         {
            q2.setParameter("name", criteria.getFilter().replaceAll("\\*", "%"));
         }
      } 
      
      if (criteria != null && criteria.isPaged() && !criteria.isFiltered())
      {
         q1.setFirstResult(criteria.getFirstResult());
         if (criteria.getMaxResults() > 0)
         {
            q1.setMaxResults(criteria.getMaxResults());
         }
         
         if (q2 != null)
         {
            q2.setFirstResult(criteria.getFirstResult());
            if (criteria.getMaxResults() > 0)
            {
               q2.setMaxResults(criteria.getMaxResults());
            }
         }
      }
      
      List<HibernateObject> result = q1.list();
      List<IdentityObject> finalResult = new ArrayList<IdentityObject>();
      for (HibernateObject hObj : result)
      {         
         finalResult.add(new SimpleIdentityObject(hObj.getName(), String.valueOf(hObj.getId()), new SimpleIdentityObjectType( getGroupTypeOfHibernateGroup((HibernateGroup)hObj))));         
      }
      
      if (q2 != null)
      {
         result = q2.list();
         for (HibernateObject hObj : result)
         {            
            finalResult.add(new SimpleIdentityObject(hObj.getName(), String.valueOf(hObj.getId()), new SimpleIdentityObjectType("USER")));         
         }         
      }
      
      return finalResult;
   }

   public String getId()
   {
      return id;
   }
   
   public void setId(String id)
   {
      this.id = id;
   }   

   public int getIdentityObjectsCount(IdentityStoreInvocationContext invocationCtx, IdentityObjectType identityType)
         throws IdentityException
   {
      return findCountOfObjectFromDBByTypeAndName("%", identityType);      
   }

   public Map<String, String> getRelationshipNameProperties(IdentityStoreInvocationContext ctx, String name) throws IdentityException,
         OperationNotSupportedException
   {  
   // return some hardcoded stuff, because we are not going to support relationsip types. So that all users will be 'member' and 'manager' .
      Map<String, String> result = new HashMap<String, String>();
      result.put("create_date", "1304422780524");
      result.put("modified_date", "1304422780524");
      result.put("description", name + " membership type");
      return result;      
   }

   public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObjectSearchCriteria criteria)
         throws IdentityException, OperationNotSupportedException
   {   
      Set<String> result = new HashSet<String>();
      
      if (criteria != null && criteria.getFilter() != null)
      {
         result.add(criteria.getFilter());
      }      
      else
      {
         result.add("manager");
         result.add("member");
      }
      
      return result;      
   }

   public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObject identity,
         IdentityObjectSearchCriteria criteria) throws IdentityException, OperationNotSupportedException
   {
      // user is always return both "manager" and "member". Group will return it only if it have some users.
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identity);
      
      if (hObject instanceof HibernateUser)
      {
         return getRelationshipNames(ctx, criteria);
      }
      else
      {
         HibernateGroup group = (HibernateGroup)hObject;
         if (group.getUsers().size() > 0)
         {
            return getRelationshipNames(ctx, criteria);
         }
         else
         {
            return new HashSet<String>();
         }
      }      
   }

   public Map<String, String> getRelationshipProperties(IdentityStoreInvocationContext ctx, IdentityObjectRelationship relationship)
         throws IdentityException, OperationNotSupportedException
   {      
      // always return new map. We are not supporting properties of relationship.
      return new HashMap<String, String>();
   }

   public void removeIdentityObject(IdentityStoreInvocationContext invocationCtx, IdentityObject identity)
         throws IdentityException
   {
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identity);
      
      if (hObject == null)
      {
         throw new IdentityException(identity + " not found in DB.");
      }
      
      Session session = getHibernateSession();
      session.delete(hObject);            
   }

   public void removeRelationship(IdentityStoreInvocationContext invocationCxt, IdentityObject fromIdentity,
         IdentityObject toIdentity, IdentityObjectRelationshipType relationshipType, String relationshipName)
         throws IdentityException
   {
      // fromIdentity is parent and toIdentity is child.
      
      // fromIdentity must be group
      HibernateObject hObjectFrom = findHibernateObjectFromIdentityObject(fromIdentity);
      HibernateObject hObjectTo = findHibernateObjectFromIdentityObject(toIdentity);
      
      HibernateGroup parentGroup;

      // fromIdentity must be group
      if (hObjectFrom instanceof HibernateUser)
      {
         throw new IdentityException("Parent object " + hObjectFrom + " is not group!");
      }
      parentGroup = (HibernateGroup)hObjectFrom;
      
      // in this case we need to remove user from group.
      if (hObjectTo instanceof HibernateUser)
      {         
         HibernateUser hUserTo = (HibernateUser)hObjectTo;
         
         // TODO: Is it better way to remove user from group with hibernate without need to select all groups of particular user?
         hUserTo.getGroups().remove(parentGroup);
      }
      else
      {
         HibernateGroup hChildGroup = (HibernateGroup)hObjectTo;
         hChildGroup.setParentGroup(null);
      }                  
   }

   public String removeRelationshipName(IdentityStoreInvocationContext ctx, String name) throws IdentityException,
         OperationNotSupportedException
   {      
      // Do nothing since we are not actively supporting relationship names
      return name;
   }

   public void removeRelationshipNameProperties(IdentityStoreInvocationContext arg0, String arg1, Set<String> arg2)
         throws IdentityException, OperationNotSupportedException
   {
      // do nothing because we are not supporting relationship name properties.      
   }

   public void removeRelationshipProperties(IdentityStoreInvocationContext arg0, IdentityObjectRelationship arg1,
         Set<String> arg2) throws IdentityException, OperationNotSupportedException
   {
      // do nothing because we are not supporting relationship name properties.            
   }

   public void removeRelationships(IdentityStoreInvocationContext invocationCtx, IdentityObject fromIdentity,
         IdentityObject toIdentity, boolean named) throws IdentityException
   {
      removeRelationship(invocationCtx, fromIdentity, toIdentity, null, null);      
   }

   public Set<IdentityObjectRelationship> resolveRelationships(IdentityStoreInvocationContext invocationCxt, IdentityObject fromIdentity,
         IdentityObject toIdentity, IdentityObjectRelationshipType relationshipType) throws IdentityException
   {
      // fromIdentity is parent and toIdentity is child.
      
      // fromIdentity must be group
      HibernateObject hObjectFrom = findHibernateObjectFromIdentityObject(fromIdentity);
      HibernateObject hObjectTo = findHibernateObjectFromIdentityObject(toIdentity);
      
      HibernateGroup parentGroup;

      // fromIdentity must be group
      if (hObjectFrom instanceof HibernateUser)
      {
         throw new IdentityException("Parent object " + hObjectFrom + " is not group!");
      }
      parentGroup = (HibernateGroup)hObjectFrom;
      
      IdentityObjectRelationshipType namedRelationshipType = new SimpleIdentityObjectRelationshipType("JBOSS_IDENTITY_ROLE");
      IdentityObjectRelationshipType unnamedRelationshipType = new SimpleIdentityObjectRelationshipType("JBOSS_IDENTITY_MEMBERSHIP");
      Set<IdentityObjectRelationship> result = new HashSet<IdentityObjectRelationship>();
      
      // in this case we are interested in users.
      if (hObjectTo instanceof HibernateUser)
      {         
         HibernateUser hUserTo = (HibernateUser)hObjectTo;
          
         // This means that user is member of group and so that he has named identityObjectRelationship of type "named role" which is JBOSS_IDENTITY_ROLE
         if (hUserTo.getGroups().contains(parentGroup))
         {            
            result.add(new SimpleIdentityObjectRelationship(fromIdentity, toIdentity, "manager", namedRelationshipType));
            result.add(new SimpleIdentityObjectRelationship(fromIdentity, toIdentity, "member",  namedRelationshipType));
         }
      }
      else
      {
         HibernateGroup hChildGroup = (HibernateGroup)hObjectTo;
         if (parentGroup.equals(hChildGroup.getParentGroup()))
         {
            result.add(new SimpleIdentityObjectRelationship(fromIdentity, toIdentity, null, unnamedRelationshipType));
         }
      }  
      
      return result;      
   }

   public Set<IdentityObjectRelationship> resolveRelationships(IdentityStoreInvocationContext invocationCxt, IdentityObject identity,
         IdentityObjectRelationshipType relationshipType, boolean parent, boolean named, String name)
         throws IdentityException
   {
      Set<IdentityObjectRelationship> result = new HashSet<IdentityObjectRelationship>();
      
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identity);
      
      if (hObject == null)
      {
         return result;
      }
      
      if (parent)
      {
         if (hObject instanceof HibernateUser)
         {
            throw new IdentityException("Object " + hObject + " must be group but it's user!");
         }
         
         HibernateGroup hGroup = (HibernateGroup)hObject;
         
         // now decide if we are looking for groups or users.
         if ("JBOSS_IDENTITY_ROLE".equals(relationshipType.getName()))
         {
            Set<HibernateUser> children = hGroup.getUsers();
            
            for (HibernateUser child : children)
            {
               IdentityObject toObject = new SimpleIdentityObject(child.getName(), new SimpleIdentityObjectType("USER"));
               
               if (name == null || "manager".equals(name))
               {
                  result.add(new SimpleIdentityObjectRelationship(identity, toObject, "manager", relationshipType));
               }
               if (name == null || "member".equals(name))
               {
                  result.add(new SimpleIdentityObjectRelationship(identity, toObject, "member", relationshipType));
               }
            }            
         }
         else if ("JBOSS_IDENTITY_MEMBERSHIP".equals(relationshipType.getName()))
         {
            Set<HibernateGroup> children = hGroup.getChildGroups();
            
            for (HibernateGroup child : children)
            {               
               IdentityObject toObject = new SimpleIdentityObject(child.getName(), new SimpleIdentityObjectType( getGroupTypeOfHibernateGroup(child)));
               if (name == null)
               {
                  result.add(new SimpleIdentityObjectRelationship(identity, toObject, null, relationshipType));
               }
            }              
         }
      }
      else
      {
         if (hObject instanceof HibernateUser)
         {
            HibernateUser hUser = (HibernateUser)hObject;
            
            Set<HibernateGroup> groups = hUser.getGroups();
            
            for (HibernateGroup group : groups)
            {               
               IdentityObject fromObject = new SimpleIdentityObject(group.getName(), new SimpleIdentityObjectType(getGroupTypeOfHibernateGroup(group)));
               if (name == null || "manager".equals(name))
               {
                  result.add(new SimpleIdentityObjectRelationship(fromObject, identity, "manager", relationshipType));
               }
               if (name == null || "member".equals(name))
               {
                  result.add(new SimpleIdentityObjectRelationship(fromObject, identity, "member", relationshipType));
               }               
            }              
         }
         else
         {
            HibernateGroup hGroup = (HibernateGroup)hObject;
            
            HibernateGroup parentGroup = hGroup.getParentGroup();
                        
            IdentityObject fromObject = new SimpleIdentityObject(parentGroup.getName(), new SimpleIdentityObjectType(getGroupTypeOfHibernateGroup(parentGroup)));
            if (name == null)
            {
               result.add(new SimpleIdentityObjectRelationship(fromObject, identity, null, relationshipType));
            }            
         }
      }
                  
      return result;
   }

   public void setRelationshipNameProperties(IdentityStoreInvocationContext arg0, String arg1, Map<String, String> arg2)
         throws IdentityException, OperationNotSupportedException
   {
      // do nothing actually. We will always return hardcoded properties.    
   }

   public void setRelationshipProperties(IdentityStoreInvocationContext arg0, IdentityObjectRelationship arg1, Map<String,String> arg2)
         throws IdentityException, OperationNotSupportedException
   {
      // do nothing because we are not supporting relationship properties.          
   }

   public void updateCredential(IdentityStoreInvocationContext ctx, IdentityObject identityObject,
         IdentityObjectCredential credential) throws IdentityException
   {
      String encodedValue = (String)credential.getEncodedValue();
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identityObject);
      
      if (hObject == null)
      {
         throw new IdentityException("Not found object " + identityObject + " in identity store.");
      }
      if (hObject instanceof HibernateGroup)
      {
         throw new IdentityException("Found object " + hObject + " which is group, but it needs to user.");
      }
      
      HibernateUser hUser = (HibernateUser)hObject;
      hUser.setPassword(encodedValue);
      
      // flush hibernate session
      getHibernateSession().flush();
   }

   public boolean validateCredential(IdentityStoreInvocationContext ctx, IdentityObject identityObject,
         IdentityObjectCredential credential) throws IdentityException
   {
      String encodedValue = (String)credential.getEncodedValue();
      
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identityObject);
     
      if (hObject == null)
      {
         throw new IdentityException("Not found object " + identityObject + " in identity store.");
      }      
      if (hObject instanceof HibernateGroup)
      {
         throw new IdentityException("Found object " + hObject + " which is group, but it needs to user.");
      }
      
      HibernateUser hUser = (HibernateUser)hObject;
      
      // no support for null password
      if (encodedValue == null)
      {
         return false;
      }
      
      return encodedValue.equals(hUser.getPassword());      
   }

   public void addAttributes(IdentityStoreInvocationContext invocationCtx, IdentityObject identity,
         IdentityObjectAttribute[] attributes) throws IdentityException
   {
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identity);
      
      Map<String, String[]> attrMap = new HashMap<String, String[]>();
      for (IdentityObjectAttribute currentAttr : attributes)
      {
         addAttributeIntoMap(attrMap, currentAttr);
      }      
      
      updateAttributesOfPersistentHibernateObject(hObject, attrMap);
      
      // flush session
      getHibernateSession().flush();
   }

   public IdentityObject findIdentityObjectByUniqueAttribute(IdentityStoreInvocationContext invocationCtx,
         IdentityObjectType identityObjectType, IdentityObjectAttribute attribute) throws IdentityException
   {
      ExampleIdentityObjectTypeEnum typeInternal = getInternalType(identityObjectType);
      
      Criteria criteria = null;
      if (ExampleIdentityObjectTypeEnum.USER.equals(typeInternal))
      {
         criteria = getHibernateSession().createCriteria(HibernateUser.class)
         .setCacheable(true);
         
         String attrName = attribute.getName();
         
         if (ATTR_USER_FIRST_NAME.equals(attrName))
         {
            criteria.add(Restrictions.eq("firstName", attribute.getValue()));
         }
         else if (ATTR_USER_LAST_NAME.equals(attrName))
         {
            criteria.add(Restrictions.eq("lastName", attribute.getValue()));
         }       
         else if (ATTR_USER_EMAIL.equals(attrName))
         {
            criteria.add(Restrictions.eq("email", attribute.getValue()));
         }   
//         else if (ATTR_USER_PASSWORD.equals(attrName))
//         {
//            criteria.add(Restrictions.eq("password", attribute.getValue()));
//         }          
         else if (ATTR_USER_CREATED_DATE.equals(attrName))
         {
            criteria.add(Restrictions.eq("createdDate", attribute.getValue()));
         }     
         else if (ATTR_USER_LAST_LOGIN_TIME.equals(attrName))
         {
            criteria.add(Restrictions.eq("lastLoginTime", attribute.getValue()));
         }  
         
      }
      else if (ExampleIdentityObjectTypeEnum.GROUP.equals(typeInternal))
      {
         criteria = getHibernateSession().createCriteria(HibernateGroup.class)
         .setCacheable(true);  
         
         String attrName = attribute.getName();
         
         if (ATTR_GROUP_LABEL.equals(attrName))
         {
            criteria.add(Restrictions.eq("label", attribute.getValue()));
         }      
         if (ATTR_GROUP_DESCRIPTION.equals(attrName))
         {
            criteria.add(Restrictions.eq("description", attribute.getValue()));
         }                
      }
      
      if (criteria == null)
      {
         return null;
      }
            
      HibernateObject result = (HibernateObject)criteria.uniqueResult();
      
      if (result == null)
      {
         return null;
      }
      return new SimpleIdentityObject(result.getName(), String.valueOf(result.getId()), identityObjectType);            
   }

   public IdentityObjectAttribute getAttribute(IdentityStoreInvocationContext invocationContext,
         IdentityObject identity, String name) throws IdentityException
   {
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identity);
      
      if (hObject == null)
      {
         throw new IdentityException("Object " + identity + " not found.");
      }
      
      if (hObject instanceof HibernateUser)
      {
         HibernateUser hUser = (HibernateUser)hObject;
         if (ATTR_USER_FIRST_NAME.equals(name))
         {
            return new SimpleAttribute(ATTR_USER_FIRST_NAME, hUser.getFirstName()); 
         }
         else if (ATTR_USER_LAST_NAME.equals(name))
         {
            return new SimpleAttribute(ATTR_USER_LAST_NAME, hUser.getLastName());
         }    
         else if (ATTR_USER_EMAIL.equals(name))
         {
            return new SimpleAttribute(ATTR_USER_EMAIL, hUser.getEmail());
         }     
         else if (ATTR_USER_CREATED_DATE.equals(name))
         {
            return new SimpleAttribute(ATTR_USER_CREATED_DATE, hUser.getCreatedDate());
         }      
         else if (ATTR_USER_LAST_LOGIN_TIME.equals(name))
         {
            return new SimpleAttribute(ATTR_USER_LAST_LOGIN_TIME, hUser.getLastLoginTime());
         }    
//         else if (ATTR_USER_PASSWORD.equals(name))
//         {
//            return new SimpleAttribute(ATTR_USER_PASSWORD, hUser.getPassword());
//         }                  
      }
      else
      {
         HibernateGroup hGroup = (HibernateGroup)hObject;
         
         if (ATTR_GROUP_LABEL.equals(name))
         {
            return new SimpleAttribute(ATTR_GROUP_LABEL, hGroup.getLabel()); 
         }     
         else if (ATTR_GROUP_DESCRIPTION.equals(name))
         {
            return new SimpleAttribute(ATTR_GROUP_DESCRIPTION, hGroup.getDescription()); 
         }          
      }
      
      return null;
   }

   public Map<String, IdentityObjectAttribute> getAttributes(IdentityStoreInvocationContext invocationContext, IdentityObject identity)
         throws IdentityException
   {
      Map<String, IdentityObjectAttribute> result = new HashMap<String, IdentityObjectAttribute>();
      
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identity);
      
      if (hObject == null)
      {
         throw new IdentityException("Object " + identity + " not found.");
      }
      
      if (hObject instanceof HibernateUser)
      {
         HibernateUser hUser = (HibernateUser)hObject;
         
         String fn = hUser.getFirstName();
         String ln = hUser.getLastName();
         String email = hUser.getEmail();
         String password = hUser.getPassword();
         Long crDate = hUser.getCreatedDate();
         Long lltime = hUser.getLastLoginTime();
         
         if (fn != null)
         {
            result.put(ATTR_USER_FIRST_NAME, new SimpleAttribute(ATTR_USER_FIRST_NAME, fn));
         }
         
         if (ln != null)
         {
            result.put(ATTR_USER_LAST_NAME, new SimpleAttribute(ATTR_USER_LAST_NAME, ln));
         }    
         
//         if (password != null)
//         {
//            result.put(ATTR_USER_PASSWORD, new SimpleAttribute(ATTR_USER_PASSWORD, password));
//         }  
         
         if (email != null)
         {
            result.put(ATTR_USER_EMAIL, new SimpleAttribute(ATTR_USER_EMAIL, email));
         }  
         
         if (crDate != null)
         {
            result.put(ATTR_USER_CREATED_DATE, new SimpleAttribute(ATTR_USER_CREATED_DATE, crDate));
         }    
         
         if (lltime != null)
         {
            result.put(ATTR_USER_LAST_LOGIN_TIME, new SimpleAttribute(ATTR_USER_LAST_LOGIN_TIME, lltime));
         }                            
                
      }
      else
      {
         HibernateGroup hGroup = (HibernateGroup)hObject;
         
         String label = hGroup.getLabel();
         String description = hGroup.getDescription();         
         
         if (label != null)
         {
            result.put(ATTR_GROUP_LABEL, new SimpleAttribute(ATTR_GROUP_LABEL, label));
         }     
         if (description != null)
         {
            result.put(ATTR_GROUP_DESCRIPTION, new SimpleAttribute(ATTR_GROUP_DESCRIPTION, description));
         }          
      }
      
      return result;
   }

   public Map<String, IdentityObjectAttributeMetaData> getAttributesMetaData(IdentityStoreInvocationContext invocationContext, IdentityObjectType identityType)
   {
      return attributesMetaData.get(identityType.getName());
   }

   public Set<String> getSupportedAttributeNames(IdentityStoreInvocationContext invocationContext,
         IdentityObjectType identityType) throws IdentityException
   {
      // We are returning empty set by default from this method.
      return new HashSet<String>();
   }

   public void removeAttributes(IdentityStoreInvocationContext invocationCtx, IdentityObject identity,
         String[] attributeNames) throws IdentityException
   {
      HibernateObject hObject = findHibernateObjectFromIdentityObject(identity);
      
      if (hObject == null)
      {
         throw new IdentityException("Object " + identity + " not found.");
      }
      
      if (hObject instanceof HibernateUser)
      {
         HibernateUser hUser = (HibernateUser)hObject;
         
         for (String attrName : attributeNames)
         {
            if (attrName.equals(ATTR_USER_FIRST_NAME))
            {
               hUser.setFirstName(null);
            }
            else if (attrName.equals(ATTR_USER_LAST_NAME))
            {
               hUser.setLastName(null);
            }       
            else if (attrName.equals(ATTR_USER_EMAIL))
            {
               hUser.setEmail(null);
            } 
//            else if (attrName.equals(ATTR_USER_PASSWORD))
//            {
//               hUser.setPassword(null);
//            }      
            else if (attrName.equals(ATTR_USER_CREATED_DATE))
            {
               hUser.setCreatedDate(null);
            }        
            else if (attrName.equals(ATTR_USER_LAST_LOGIN_TIME))
            {
               hUser.setLastLoginTime(null);
            }               
         }
      }
      else
      {
         HibernateGroup hGroup = (HibernateGroup)hObject;
             
         for (String attrName : attributeNames)
         {
            if (attrName.equals(ATTR_GROUP_LABEL))
            {
               hGroup.setLabel(null);
            }      
            else if (attrName.equals(ATTR_GROUP_DESCRIPTION))
            {
               hGroup.setDescription(null);
            }                
         }
      }
      
   }

   public void updateAttributes(IdentityStoreInvocationContext invocationCtx, IdentityObject identity,
         IdentityObjectAttribute[] attributes) throws IdentityException
   {
      addAttributes(invocationCtx, identity, attributes);      
   }
   
   public boolean isManageTransactionDuringBootstrap()
   {
      return isManageTransactionDuringBootstrap;
   }

   public boolean isAllowNotCaseSensitiveSearch()
   {
      return isAllowNotCaseSensitiveSearch;
   }  
   
   /*** HELPER METHODS ***/
   
   /**
    * Add new user or update existing user
    */
   public void persistHibernateObject(HibernateObject object) throws IdentityException
   {
      Session hSession = getHibernateSession();
      
      hSession.persist(object);
      hSession.flush();
   }   
   
   public void addSubgroupsToGroup(HibernateGroup parentGroup, Set<HibernateGroup> childGroups) throws IdentityException
   {
      parentGroup.getChildGroups().addAll(childGroups);
      
      Session hSession = getHibernateSession();
      hSession.persist(parentGroup);
      hSession.flush();
   }
   
   private Session getHibernateSession() throws IdentityException
   {
      try
      {
         return (Session)createIdentityStoreSession().getSessionContext();
      }
      catch (Exception e)
      {
         if (log.isLoggable(Level.FINER))
         {
            log.log(Level.FINER, "Exception occurred: ", e);
         }

         throw new IdentityException("Cannot obtain Hibernate Session", e);
      }
   } 
   
   
   
   
   private ExampleIdentityObjectTypeEnum getInternalType(IdentityObjectType type)
   {
      if ("USER".equals(type.getName()))
      {
         return ExampleIdentityObjectTypeEnum.USER;
      }
      else
      {
         return ExampleIdentityObjectTypeEnum.GROUP;
      }
   }
   
   
   
   
   private HibernateObject buildPopulatedHibernateObject(String name, ExampleIdentityObjectTypeEnum typeInternal, Map<String, String[]> attributes)
   {
      if (typeInternal.equals(ExampleIdentityObjectTypeEnum.USER))
      {
         // created date
         long crDate = GenericHelper.parseLongSafe(getAttributeValueFromMap(attributes, ATTR_USER_CREATED_DATE));
         if (crDate == GenericHelper.PARSE_ERROR_FLAG)
         {
            crDate = System.currentTimeMillis();
         }
         
         // last login time
         long llt = GenericHelper.parseLongSafe(getAttributeValueFromMap(attributes, ATTR_USER_LAST_LOGIN_TIME));
         if (llt == GenericHelper.PARSE_ERROR_FLAG)
         {
            llt = System.currentTimeMillis();
         }         
         
         HibernateUser user = new HibernateUser(name, 
               getAttributeValueFromMap(attributes, ATTR_USER_PASSWORD),
               getAttributeValueFromMap(attributes, ATTR_USER_FIRST_NAME),
               getAttributeValueFromMap(attributes, ATTR_USER_LAST_NAME),
               getAttributeValueFromMap(attributes, ATTR_USER_EMAIL),
               crDate,
               llt
               ); 
         return user;
      }
      else
      {
         HibernateGroup group = new HibernateGroup(name,
               getAttributeValueFromMap(attributes, ATTR_GROUP_LABEL),
               getAttributeValueFromMap(attributes, ATTR_GROUP_DESCRIPTION),
               null
               );
         return group;
      }
      
   }
   
   private void updateAttributesOfPersistentHibernateObject(HibernateObject hObject, Map<String, String[]> attributes)
   {
      if (hObject instanceof HibernateUser)
      {
         HibernateUser hUser = (HibernateUser)hObject;
         
         // created date
         long crDate = GenericHelper.parseLongSafe(getAttributeValueFromMap(attributes, ATTR_USER_CREATED_DATE));
         if (crDate != GenericHelper.PARSE_ERROR_FLAG)
         {
            hUser.setCreatedDate(crDate);
         }
         
         // last login time
         long llt = GenericHelper.parseLongSafe(getAttributeValueFromMap(attributes, ATTR_USER_LAST_LOGIN_TIME));
         if (llt != GenericHelper.PARSE_ERROR_FLAG)
         {
            hUser.setLastLoginTime(llt);
         }  
         
         // password
         String password = getAttributeValueFromMap(attributes, ATTR_USER_PASSWORD);
         if (password != null)
         {
            hUser.setPassword(password);
         }
         
         // first name
         String fn = getAttributeValueFromMap(attributes, ATTR_USER_FIRST_NAME);
         if (fn != null)
         {
            hUser.setFirstName(fn);
         }         
         
         // last name
         String ln = getAttributeValueFromMap(attributes, ATTR_USER_LAST_NAME);
         if (ln != null)
         {
            hUser.setLastName(ln);
         }         
         
         
         // email
         String email = getAttributeValueFromMap(attributes, ATTR_USER_EMAIL);
         if (email != null)
         {
            hUser.setEmail(email);
         }           
         
      }
      else
      {
         HibernateGroup hGroup = (HibernateGroup)hObject;   
         
         // label
         String label = getAttributeValueFromMap(attributes, ATTR_GROUP_LABEL);
         if (label != null)
         {
            hGroup.setLabel(label);
         }      
         
         // description
         String description = getAttributeValueFromMap(attributes, ATTR_GROUP_DESCRIPTION);
         if (description != null)
         {
            hGroup.setDescription(description);
         }            
      }
   }
   
   private void addAttributeIntoMap(Map<String, String[]> attrMap, IdentityObjectAttribute attr)
   {
      // we are not supporting multiple values of single attribute.
      attrMap.put( attr.getName(), 
                   new String[] { (String)attr.getValue() 
                 });
   }     
   
   
   private String getAttributeValueFromMap(Map<String, String[]> attributes, String attributeKey)
   {
      if (attributes == null)
      {
         return null;
      }
      
      String[] val = attributes.get(attributeKey);
      if (val == null || val.length == 0)
      {
         return null;
      }
      else
      {
         return val[0];
      }      
   }
   
   
   
   
   private int findCountOfObjectFromDBByTypeAndName(String name, IdentityObjectType ioType) throws IdentityException
   {
      Number count;
      Session session = getHibernateSession();
      ExampleIdentityObjectTypeEnum internalType = getInternalType(ioType);
      
      if (internalType.equals(ExampleIdentityObjectTypeEnum.USER))
      {         
         count = (Number)session.createCriteria(HibernateUser.class)
            .add(Restrictions.like("name", name))
            .setProjection(Projections.rowCount())
            .setCacheable(true)
            .list().get(0);     
         
      }
      else
      {
         count = (Number)session.createCriteria(HibernateGroup.class)
         .add(Restrictions.like("name", name))
         .setProjection(Projections.rowCount())
         .setCacheable(true)
         .list().get(0);           
      }
      return count.intValue();
   }
   
   
   private HibernateObject findHibernateObjectFromIdentityObject(IdentityObject iObject) throws IdentityException
   {
      Session session = getHibernateSession();      
      
      if ("USER".equals(iObject.getIdentityType().getName()))
      {
         return (HibernateObject)session.createCriteria(HibernateUser.class)
         .add(Restrictions.eq("name", iObject.getName()))
         .setCacheable(true)
         .uniqueResult();
      }
      else
      {
         return (HibernateObject)session.createCriteria(HibernateGroup.class)
         .add(Restrictions.eq("name", iObject.getName()))
         .setCacheable(true)
         .uniqueResult();         
      }      
   }
   
   
   
   
   private Criteria convertCriteriaForFindingHibernateObjects(IdentityObjectSearchCriteria criteria, ExampleIdentityObjectTypeEnum typeInternal) throws IdentityException
   {
      Class<? extends HibernateObject> clazz = typeInternal.equals(ExampleIdentityObjectTypeEnum.USER) ? HibernateUser.class : HibernateGroup.class;
      
      Criteria hc = getHibernateSession().createCriteria(clazz)
      .setCacheable(true);
      
      if (criteria != null && criteria.isSorted())
      {
         if (criteria.isAscending())
         {
            hc.addOrder(Order.asc("name"));
         }
         else
         {
            hc.addOrder(Order.desc("name"));
         }
      }

      if (criteria != null && criteria.isPaged() && !criteria.isFiltered())
      {
         if (criteria.getMaxResults() > 0)
         {
            hc.setMaxResults(criteria.getMaxResults());
         }
         hc.setFirstResult(criteria.getFirstResult());
      }

      if (criteria != null && criteria.getFilter() != null)
      {
         hc.add(Restrictions.like("name", criteria.getFilter().replaceAll("\\*", "%")));
      }
      else
      {
         hc.add(Restrictions.like("name", "%"));
      } 
      
      Map<String, String[]> attributes = criteria==null ? new HashMap<String, String[]>() : criteria.getValues();
      Map<String, String> filteredAttributes = new HashMap<String, String>();
      
      if (ExampleIdentityObjectTypeEnum.USER.equals(typeInternal))
      {
         filteredAttributes.put(ATTR_USER_PASSWORD, getAttributeValueFromMap(attributes, ATTR_USER_PASSWORD));
         filteredAttributes.put(ATTR_USER_FIRST_NAME, getAttributeValueFromMap(attributes, ATTR_USER_FIRST_NAME));
         filteredAttributes.put(ATTR_USER_LAST_NAME, getAttributeValueFromMap(attributes, ATTR_USER_LAST_NAME));
         filteredAttributes.put(ATTR_USER_EMAIL, getAttributeValueFromMap(attributes, ATTR_USER_EMAIL));
         filteredAttributes.put(ATTR_USER_CREATED_DATE, getAttributeValueFromMap(attributes, ATTR_USER_CREATED_DATE));
         filteredAttributes.put(ATTR_USER_LAST_LOGIN_TIME, getAttributeValueFromMap(attributes, ATTR_USER_LAST_LOGIN_TIME));
      }
      else
      {
         filteredAttributes.put(ATTR_GROUP_LABEL, getAttributeValueFromMap(attributes, ATTR_GROUP_LABEL));
         filteredAttributes.put(ATTR_GROUP_DESCRIPTION, getAttributeValueFromMap(attributes, ATTR_GROUP_DESCRIPTION));
      }
      
      for (Map.Entry<String, String> entry : filteredAttributes.entrySet())
      {
         if (entry.getValue() != null)
         {
            hc.add(Restrictions.like(entry.getKey(), entry.getValue().replaceAll("\\*", "%")));
         }
      }
      
      return hc;
      
   }
   
   private String getGroupTypeOfHibernateGroup(HibernateGroup group)
   {
      if (group.getParentGroup() == null)
      {
         return "root_type";
      }
      
      StringBuilder result = new StringBuilder();
      while (group.getParentGroup() != null)
      {
         group = group.getParentGroup();
         result.append("." + group.getName());
      }
      
      return result.toString();
   }

}

